package twoD.hofem;

import java.util.ArrayList;

/**
 * Piecewise polynomial basis on R. This is basically a set of basis S = {phi1,
 * phi2, ...,phi} where phii: R->R.
 * 
 * Author by Whitehorse
 */

public class PiecewisePolynomialBasisOnR implements FunctionBasisOnR, Cloneable {

	private double[] nodes_;
	private int[] pVector_;
	private double[] xHat_;
	private PiecewiseFunctionRToR[] basisFuctions_;
	private PolynomialBasisOnR masterBasis_;

	/**
	 * set master basis (Lagrange function base or Hierarchic function base)
	 * 
	 * @param masterBasis
	 */
	public void setMasterBasis(PolynomialBasisOnR masterBasis) {
		masterBasis_ = masterBasis;
	}

	/**
	 * set the nodes
	 * 
	 * @param nodes
	 *            array of the nodes
	 */
	public void setNodes(double... nodes) {
		nodes_ = nodes;
	}

	/**
	 * set the polynomial degree vector
	 * 
	 * @param ps
	 *            array of the polynomial degrees
	 */
	public void setPVector(int... ps) {
		pVector_ = ps;
	}

	/**
	 * set the constraints
	 * 
	 * @param xHat
	 *            array of the constraints
	 */
	public void setXHats(double... xHat) {
		xHat_ = xHat;
	}

	/**
	 * Populate basisFunctions array with properties which are set
	 * 
	 */
	public void update() {
		/*
		 * 1. make nodal basis ( ignore when the node is constrained ) 2. make
		 * line basis
		 */
		ArrayList<PiecewiseFunctionRToR> basisList = new ArrayList<PiecewiseFunctionRToR>();

		// 1. make nodal basis
		for (int i = 0; i < nodes_.length; i++) {
			if (isConstrained(nodes_[i]) == false) {
				FunctionRToR[] pieceOfBS = new FunctionRToR[this
						.getNumberOfPieces()];
				if (i == 0) {
					// starting node
					masterBasis_.setP(pVector_[i]);
					LinearFunctionRToR invQ = new LinearFunctionRToR(nodes_[i],
							nodes_[i + 1], -1, 1);
					pieceOfBS[0] = FMath.compose(
							distinguish(1, 0, masterBasis_)[0], invQ);
					for (int j = 1; j < this.getNumberOfPieces(); j++) {
						pieceOfBS[j] = new NullFunctionRToR(nodes_[j],
								nodes_[j + 1]);
					}
				} else if (i == nodes_.length - 1) {
					// ending node
					masterBasis_.setP(pVector_[i - 1]);
					LinearFunctionRToR invQ = new LinearFunctionRToR(nodes_[i - 1],
							nodes_[i], -1, 1);
					pieceOfBS[this.getNumberOfPieces() - 1] = FMath.compose(
							distinguish(0, 1, masterBasis_)[0], invQ);
					for (int j = 0; j < this.getNumberOfPieces() - 1; j++) {
						pieceOfBS[j] = new NullFunctionRToR(nodes_[j],
								nodes_[j + 1]);
					}
				} else {
					// front null function
					for (int j = 0; j < i - 1; j++) {
						pieceOfBS[j] = new NullFunctionRToR(nodes_[j],
								nodes_[j + 1]);
					}

					// inter part
					masterBasis_.setP(pVector_[i - 1]);
					LinearFunctionRToR invQ1 = new LinearFunctionRToR(nodes_[i - 1],
							nodes_[i], -1, 1);
					pieceOfBS[i - 1] = FMath.compose(distinguish(0, 1,
							masterBasis_)[0], invQ1);
					masterBasis_.setP(pVector_[i]);
					LinearFunctionRToR invQ2 = new LinearFunctionRToR(nodes_[i],
							nodes_[i + 1], -1, 1);
					pieceOfBS[i] = FMath.compose(
							distinguish(1, 0, masterBasis_)[0], invQ2);

					// back null function
					for (int j = i + 1; j < this.getNumberOfPieces(); j++) {
						pieceOfBS[j] = new NullFunctionRToR(nodes_[j],
								nodes_[j + 1]);
					}
				}
				basisList.add(new PiecewiseFunctionRToR(pieceOfBS));
			} else {
			}
		}

		// 2. make line basis
		for (int i = 0; i < this.getNumberOfPieces(); i++) {
			FunctionRToR[] pieceOfBS = new FunctionRToR[this
					.getNumberOfPieces()];
			masterBasis_.setP(pVector_[i]);

			// front null function
			for (int j = 0; j < i; j++) {
				pieceOfBS[j] = new NullFunctionRToR(nodes_[j], nodes_[j + 1]);
			}
			// back null function
			for (int j = i + 1; j < this.getNumberOfPieces(); j++) {
				pieceOfBS[j] = new NullFunctionRToR(nodes_[j], nodes_[j + 1]);
			}

			// inter part
			FunctionRToR[] interpart = distinguish(0, 0, masterBasis_);
			for (int k = 0; k < interpart.length; k++) {

				LinearFunctionRToR invQ = new LinearFunctionRToR(nodes_[i],
						nodes_[i + 1], -1, 1);
				pieceOfBS[i] = FMath.compose(interpart[k], invQ);

				FunctionRToR[] pieceOfBS2 = new FunctionRToR[this
						.getNumberOfPieces()];
				for (int l = 0; l < pieceOfBS.length; l++) {
					pieceOfBS2[l] = pieceOfBS[l];
				}

				basisList.add(new PiecewiseFunctionRToR(pieceOfBS2));
			}
		}
		basisFuctions_ = (PiecewiseFunctionRToR[]) basisList
				.toArray(new PiecewiseFunctionRToR[basisList.size()]);
	}

	/**
	 * Returns shape functions which satisfy specific boundary condition There
	 * are three conditions (1,0) f(-1)=1 , f(1)=0 
	 *                      (0,1) f(-1)=0 , f(1)=1 
	 *                      (0,0) f(-1)=0 , f(1)=0
	 * @param a, b, fbs 
	 *        a : lower boundary condition , b : upper boundary condition , fbs : a array of the shape functions
	 * @return an array of the selected shape function
	 */
	public FunctionRToR[] distinguish(int a, int b, PolynomialBasisOnR fbs) {
		ArrayList<FunctionRToR> fbList = new ArrayList<FunctionRToR>();

		if (a == 1 && b == 0) {
			for (int i = 0; i < fbs.getP() + 1; i++) {
				if (Math.abs(fbs.getBasisFunction(i).valueAt(-1)) >= 0.9
						&& Math.abs(fbs.getBasisFunction(i).valueAt(-1)) <= 1.1
						&& Math.abs(fbs.getBasisFunction(i).valueAt(1)) <= 1e-10) {
					fbList.add(fbs.getBasisFunction(i));
				}
			}
		} else if (a == 0 && b == 1) {
			for (int i = 0; i < fbs.getP() + 1; i++) {
				if (Math.abs(fbs.getBasisFunction(i).valueAt(-1)) <= 1e-10
						&& Math.abs(fbs.getBasisFunction(i).valueAt(1)) >= 0.9
						&& Math.abs(fbs.getBasisFunction(i).valueAt(1)) <= 1.1) {
					fbList.add(fbs.getBasisFunction(i));
				}
			}
		} else {
			for (int i = 0; i < fbs.getP() + 1; i++) {
				if (Math.abs(fbs.getBasisFunction(i).valueAt(-1)) <= 1e-10
						&& Math.abs(fbs.getBasisFunction(i).valueAt(1)) <= 1e-10) {
					fbList.add(fbs.getBasisFunction(i));
				}
			}
		}
		return (FunctionRToR[]) fbList.toArray(new FunctionRToR[fbList.size()]);
	}

	/**
	 * Returns number of pieces of basis set.
	 * 
	 * @return number of pieces
	 */
	public int getNumberOfPieces() {
		return (nodes_.length) - 1;
	}

	/**
	 * Returns "true" if the node is constrained
	 * 
	 * @param x
	 *            position x of node
	 * @return true or false
	 */
	private boolean isConstrained(double x) {
		boolean answer = false;

		if (xHat_ != null) {

			for (int i = 0; i < xHat_.length; i++) {
				if (x == xHat_[i]) {
					answer = true;
				}
			}
		}
		return answer;
	}

	/**
	 * Returns the specified shape function.
	 * 
	 * @param idx
	 *            index of the shape function
	 * @return the shape function
	 */
	public FunctionRToR getBasisFunction(int idx) {
		return basisFuctions_[idx];
	}

	/**
	 * Returns all basis functions.
	 * 
	 * @return an array of functions
	 */
	public FunctionRToR[] getBasisFunctions() {
		return basisFuctions_;
	}

	/**
	 * Returns the breakpoints for the basis functions.
	 * 
	 * @return array of breakpoints or <code>null</code>
	 */
	public double[] getBreakpoints() {
		double[] breakpoints = new double[nodes_.length - 2];
		for (int i = 0; i < nodes_.length - 2; i++) {
			breakpoints[i] = nodes_[i + 1];
		}
		return breakpoints;
	}

	/**
	 * Returns the number of basis functions.
	 * 
	 * @return the dimension
	 */
	public int getDimension() {
		return basisFuctions_.length;
	}

	/**
	 * Returns the domain on which this basis is defined.
	 * 
	 * @return the domain
	 */
	public Interval getDomain() {
		double lower = nodes_[0];
		double upper = nodes_[0];
		for (int i = 0; i < (nodes_.length - 1); i++) {
			lower = Math.min(lower, nodes_[i + 1]);
			upper = Math.max(upper, nodes_[i + 1]);
		}
		return new Interval(lower, upper);
	}

	/**
	 * Returns the maximum p for all functions.
	 * 
	 * @return max p
	 */
	public int getP() {
		int maxP = pVector_[0];
		for (int i = 0; i < (pVector_.length - 1); i++) {
			maxP = Math.max(maxP, pVector_[i + 1]);
		}
		return maxP;
	}

	/**
	 * Returns pVector
	 * 
	 * @return pVector
	 */
	public int[] getPVector() {
		return pVector_;
	}

	/**
	 * Returns xHat
	 * 
	 * @return xHat
	 */
	public double[] getXHat() {
		return xHat_;
	}

	/**
	 * Returns modes
	 * 
	 * @return nodes
	 */
	public double[] getNodes() {
		return nodes_;
	}

	/**
	 * Returns master basis
	 * 
	 * @return master basis
	 */
	public PolynomialBasisOnR getMasterBasis() {
		return masterBasis_;
	}
	
	/*
	 * (non-Javadoc)
	 * @see java.lang.Object#clone()
	 * 
	 * Added by Team 1 in order to be able to copy this object.
	 */
	public Object clone() {
		Object o = null;
		try {
			o = super.clone();
		} catch(CloneNotSupportedException e) {
			System.err.println("PiecewisePolynomialBasisOnR can't clone!");
		}
		return o;
	}
}
